home *** CD-ROM | disk | FTP | other *** search
/ Windows Expert / Windows Expert.iso / utility / addmnu34.zip / SOURCE.ZIP / ADDMENU.C next >
C/C++ Source or Header  |  1992-05-12  |  32KB  |  1,071 lines

  1. /* AddMenu, v0.3.4
  2.  *
  3.  * This program is notably inefficient insofar as every change that is save is written to
  4.  * the profile file.  Granted, now that we all use disk caches that's not really a problem,
  5.  * but I admit that it isn't great programming technique.  I'm lazy.
  6.  *
  7.  * The program, AddMenu, is designed to let you add options to your system menus.  When
  8.  * running, it hooks into the system menus to add the specified options to all menus.  It
  9.  * also hooks into the keyboard and mouse to intercept requests for those options in order
  10.  * to execute those commands.
  11.  *
  12.  * Please appreciate that this is version 0.  This means that it is a pre-release release.
  13.  * In other words, I appreciate that there are problems with the code, but I don't have
  14.  * time to worry about it right now.  If you find bugs, please let me know, but don't
  15.  * expect a timely response.  But by all means, let me know what you think.
  16.  *
  17.  * There are many notable limitations/problems with the program as it currently exists:
  18.  *
  19.  *   1.    No control of the order of items on the menu.
  20.  *   2.    Program currently uses both WH_CALLWNDPROC and WH_GETMESSAGE hooks.  The first
  21.  *    adversely affects system performance.
  22.  *   3.    This program rather arbitrarily uses menu id's starting at 0xE120.  This is intended
  23.  *    to minimized conflicts with existing system menus (0xF000 and higher) and likely
  24.  *    ids used by other programs.  It is possible that these could conflict with other
  25.  *    programs.  By adding a line "First Id=n" to the profile, where "n" is some number,
  26.  *    can control what id should be used.  I didn't know how else to get around possible
  27.  *    conflicts.
  28.  *   4.    No help file yet.
  29.  *   5. And more, as of yet undiscovered, problems ...
  30.  *
  31.  * Program requires COMMDLG.DLL, which comes with Windows 3.1 (but can be distributed with
  32.  * the program).  Program also requires AMFILTER.DLL which installs the two filters and handles
  33.  * the menu events and posts the appropriate private messages to this program.
  34.  *
  35.  * This program is copywrite of Robert M. Ryan, 1992.  It is provided without warrantee
  36.  * of any sort.  This program is FreeWare, and can be used and distributed for
  37.  * non-commercial use without fee providing that:
  38.  *
  39.  *     a) it is not to be altered without my permission;
  40.  *    b) my name remains on the package at all times;
  41.  *    c) any programs which employ code taken from this program must credit me for
  42.  *       the appropriate routines; and
  43.  *    d) no fee is ever charged for the distribution of the program short of the cost
  44.  *       of disk media and shipping cost (if any).
  45.  *
  46.  * If you want to use it for commercial purposes or have any questions about these policies,
  47.  * do not hesitate to contact me.
  48.  *
  49.  * v0.3.0, Robert M. Ryan, 28 April 1992, first public release
  50.  * v0.3.1
  51.  *    - fix exit bug
  52.  *    - modify so combo box works like it's supposed to
  53.  * v0.3.2
  54.  *    - remove extraneous check of WM_MOVE (fixed by 0.3.1)
  55.  *    - extend "executables" to include "*.exe;*.com;*.pif;*.bat"
  56.  * v0.3.3, 9 May 1992
  57.  *    - Change filter to {"Programs","*.exe;*.com;*.pif;*.bat","All Files","*.*"}
  58.  * v0.3.4, 12 May 1992
  59.  *    - Change "All Files" to "All Files *.*"
  60.  *
  61.  * Rob Ryan
  62.  *    internet:   Robert_Ryan@brown.edu
  63.  *    bitnet:     ST802200@BROWNVM.BITNET
  64.  *    Compu$erve: 70324,227
  65.  */
  66.  
  67.  
  68. /* global defines */
  69.  
  70. #define PROGNAME    "AddMenu"
  71. #define VERSION        PROGNAME " v0.3.4"
  72. #define VERDATE        __DATE__
  73. #define PROGRAMMER    "Robert M. Ryan"
  74. #define STRING_MAX    256            /* COMMDLG requires at least 256*/
  75. #define IDM_FIRST    0xE120            /* default first id for additions to system menu */
  76. #define IDM_INCREMENT    0x10            /* increment between successive ids */
  77. #define HANDLE_MAX    200            /* how many parent handles can we enumerate? */
  78. #define BUF_SIZE    8000            /* size of buffer to hold ini stuff */
  79. #define WILDCARDS    "*.exe;*.com;*.pif;*.bat" /* what wildcards for browse command, 0.3.2 */
  80. #define TICKTHRESH    1000            /* minimium GetTickCount() interval */
  81. enum SeparatorTypes {SEP_NONE = 0, SEP_LINE, SEP_BREAK};
  82.  
  83.  
  84. /****************************************************************************
  85.  * include files
  86.  ****************************************************************************/
  87.  
  88. #include <windows.h>
  89. #include <stdarg.h>
  90. #include <stdio.h>
  91. #include <stdlib.h>
  92. #include <commdlg.h>
  93.  
  94. #include "addmenu.h"
  95. #include "amfilter.h"
  96.  
  97.  
  98. /****************************************************************************
  99.  * type definitions
  100.  ****************************************************************************/
  101.  
  102. typedef struct tagMenuLst {        /* used for linked list of menu commands */
  103.     PSTR          szText;
  104.     PSTR          szCommand;
  105.     struct tagMenuLst *pNext;
  106. } MENULST;
  107.  
  108. typedef HWND FAR *LPHWND;
  109.  
  110.  
  111. /****************************************************************************
  112.  * global variables
  113.  ****************************************************************************/
  114.  
  115. LPSTR    lpClass        = PROGNAME;
  116. HANDLE    hInst;
  117. HWND    hwndMain;            /* window handle of main program */
  118. HWND    hwndDialog;            /* window handle of main dialog */
  119. HWND    hwndCombo;            /* window handle of combobox */
  120. FARPROC    lpitMain;            /* instance thunk of main dialog */
  121.  
  122. /* misc .ini variables */
  123.  
  124. LPSTR    lpHidden    = "Hidden";
  125. LPSTR    lpSeparator = "Separator";
  126. LPSTR    lpFirst        = "First Id";
  127. LPSTR    lpOptions   = "Options";
  128. LPSTR    lpProfile   = "addmenu.ini";
  129.  
  130. /* misc program status variables */
  131.  
  132. BOOL    bHidden;            /* is icon hidden? */
  133. WORD    nSeparator;            /* either SEP_NONE, SEP_LINE, or SEP_BREAK */
  134. WORD    wFirst;                /* what is the first id to be used */
  135. BOOL    bChanges;            /* are there any changes to save? */
  136. BOOL    bChangesMade;            /* have any changes been saved? */
  137.  
  138. /* Keep track of time that command was last executed.  This is here
  139.  * because Write (in Windows 3.1) apparently sends two commands.  It
  140.  * is the only application that I've seen that does it, but to prevent
  141.  * problems, I check these variables and make sure that TICKTHRES msec
  142.  * have taken place before issuing the same command
  143.  */
  144. DWORD    dwLastCmdTicks;            /* when was the last PM_MENUOPTION (in msec) */
  145. WORD    wLastCmd;            /* and what was it */
  146.  
  147. /* the base for the linked list of menu options */
  148.  
  149. MENULST    mlFirst = { NULL, NULL, NULL };
  150.  
  151. /* variables used while editing dialog box contents */
  152.  
  153. char    szMenuName[STRING_MAX];
  154. LPSTR    lpMenuName = szMenuName;
  155. char    szPrevName[STRING_MAX];
  156. LPSTR    lpPrevName = szMenuName;
  157. char    szFilename[STRING_MAX];
  158. LPSTR    lpFilename = szFilename;
  159.  
  160. /* variables used in GetOpenFileName() from COMMDLG.H */
  161.  
  162. LPSTR    lpFilter = "Programs\0" WILDCARDS "\0All Files (*.*)\0*.*\0\0\0";
  163. LPSTR    lpPick = "Browse";
  164. LPSTR    lpExe = "exe";
  165.  
  166. /* variables use in the enumeration of windows */
  167.  
  168. LPHWND    hwndArray;
  169. int    nCountArray;
  170. int     nCountWindow;
  171.  
  172.  
  173. /****************************************************************************
  174.  * function prototypes
  175.  ****************************************************************************/
  176.  
  177. void ReadIni(void);
  178. LONG FAR PASCAL WndProc(HWND hWnd, WORD wMsg, WORD wParam, LONG lParam);
  179. int  FAR PASCAL AboutDlg(HWND hDlg, WORD wMsg, WORD wParam, LONG lParam);
  180. int  FAR PASCAL MainDlg(HWND hDlg, WORD wMsg, WORD wParam, LONG lParam);
  181. void ErrorBox(HWND hWnd, PSTR szFormat, ...);
  182. void Modal(HWND hWnd, LPSTR lpName, FARPROC lpFunc);
  183. void WritePrivateProfileInt(LPSTR lpAppName, LPSTR lpKeyName, int nInt, LPSTR lpFileName);
  184. int  FindExactInCB(HWND hDlg, int nIdd, LPSTR lpName);
  185. void EnableMenus(HWND hWnd);
  186. void PickFile(HWND hWnd);
  187. void ProcessWindows(void);
  188. BOOL FAR PASCAL EnumWindowsFunc(HWND hWnd, DWORD lParam);
  189. BOOL FAR PASCAL EnumChildrenFunc(HWND hWnd, DWORD lParam);
  190. void FixWindow(HWND hWnd);
  191.  
  192.  
  193. /****************************************************************************
  194.  * the code
  195.  ****************************************************************************/
  196.  
  197. #pragma argsused
  198.  
  199. int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  200. {
  201.     WNDCLASS    wndclass;
  202.     MSG        msg;
  203.     LONG    lDlgBaseUnits = GetDialogBaseUnits();
  204.     int        nWidth, nHeight;
  205.     HWND    hWnd;
  206.  
  207.     /* if already running, make it visible if its not already, and give focus */
  208.  
  209.     if (hPrevInstance != NULL) {
  210.     hWnd = FindWindow(lpClass, lpClass);
  211.     ShowWindow(hWnd, SW_RESTORE);
  212.     SetFocus(hWnd);
  213.     return 0;
  214.     }
  215.  
  216.     /* read profile string parameters */
  217.     ReadIni();
  218.  
  219.     hInst      = hInstance;
  220.  
  221.     wndclass.style       = 0;
  222.     wndclass.lpfnWndProc   = WndProc;
  223.     wndclass.cbClsExtra       = 0;
  224.     wndclass.cbWndExtra       = 0;
  225.     wndclass.hInstance       = hInstance;
  226.     wndclass.hIcon       = LoadIcon(hInstance, MAKEINTRESOURCE(1));
  227.     wndclass.hCursor       = NULL;
  228.     wndclass.hbrBackground = NULL;
  229.     wndclass.lpszMenuName  = NULL;
  230.     wndclass.lpszClassName = lpClass;
  231.  
  232.     RegisterClass(&wndclass);
  233.  
  234.     /* Make Window big enough for child dialog (exactly) */
  235.  
  236.     nWidth  = (WINDOW_WIDTH * LOWORD(lDlgBaseUnits)) / 4 +
  237.                 GetSystemMetrics(SM_CXBORDER) * 2;
  238.     nHeight = (WINDOW_HEIGHT * HIWORD(lDlgBaseUnits)) / 8 +
  239.                 GetSystemMetrics(SM_CYBORDER) * 2 +
  240.                 GetSystemMetrics(SM_CYMENU) +
  241.                 GetSystemMetrics(SM_CYCAPTION);
  242.  
  243.     hwndMain = CreateWindow(lpClass,
  244.             lpClass,
  245.             WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU, /* WS_OVERLAPPEDWINDOW, */
  246.             CW_USEDEFAULT, 0,
  247.             nWidth, nHeight,
  248.             NULL, LoadMenu(hInstance, MAKEINTRESOURCE(1)),
  249.             hInstance, NULL);
  250.  
  251.     /* show it as appropriate */
  252.  
  253.     ShowWindow(hwndMain, nCmdShow);
  254.  
  255.     while(GetMessage(&msg, NULL, 0, 0)) {
  256.     if (IsDialogMessage(hwndDialog, &msg))
  257.         continue;
  258.     TranslateMessage(&msg);
  259.     DispatchMessage(&msg);
  260.     }
  261.  
  262.     FreeProcInstance(lpitMain);
  263.  
  264.     return msg.wParam;
  265. } /* WinMain */
  266.  
  267.  
  268. /* ReadIni
  269.  *
  270.  * Read the contents from the private profile file.
  271.  */
  272. void ReadIni(void)
  273. {
  274.     HANDLE    hData;
  275.     LPSTR    pData;
  276.     LPSTR    pPtr;
  277.     char    szTemp[STRING_MAX];
  278.     MENULST    *pList = &mlFirst;
  279.  
  280.     bHidden    = GetPrivateProfileInt(lpClass, lpHidden, 0, lpProfile);
  281.     nSeparator = GetPrivateProfileInt(lpClass, lpSeparator, SEP_LINE, lpProfile);
  282.     wFirst     = GetPrivateProfileInt(lpClass, lpFirst, IDM_FIRST, lpProfile);
  283.  
  284.     hData = GlobalAlloc(GMEM_DISCARDABLE | GMEM_MOVEABLE, BUF_SIZE);
  285.     if (!hData) {
  286.     ErrorBox(NULL, "ReadIni: Unable to allocate memory");
  287.     return;
  288.     }
  289.  
  290.     pData = GlobalLock(hData);
  291.     if (!pData) {
  292.     ErrorBox(NULL, "ReadIni: Unable to access memory");
  293.     return;
  294.     }
  295.  
  296.     if (!GetPrivateProfileString(lpOptions, NULL, NULL, pData, BUF_SIZE, lpProfile)) {
  297.     GlobalUnlock(hData);
  298.     GlobalFree(hData);
  299.     return;
  300.     }
  301.  
  302.     pPtr = pData;
  303.     while (*pPtr) {
  304.     pList->pNext = malloc(sizeof(MENULST));
  305.     pList = pList->pNext;
  306.     if (!pList)
  307.         return;
  308.     pList->szText = malloc(lstrlen(pPtr) + 1);
  309.     lstrcpy(pList->szText, pPtr);
  310.     if (GetPrivateProfileString(lpOptions, pPtr, "", szTemp, STRING_MAX, lpProfile)) {
  311.         pList->szCommand = malloc(lstrlen(szTemp) + 1);
  312.         lstrcpy(pList->szCommand, szTemp);
  313.     } else
  314.         pList->szCommand = NULL;
  315.     pList->pNext = NULL;
  316.     pPtr += lstrlen(pPtr) + 1;
  317.     }
  318.     GlobalUnlock(hData);
  319.     GlobalFree(hData);
  320. } /* ReadIni */
  321.  
  322.  
  323. /* WndProc
  324.  *
  325.  *    The main window's callback function.
  326.  */
  327.  
  328. #pragma argsused
  329.  
  330. LONG FAR PASCAL WndProc(HWND hWnd, WORD wMsg, WORD wParam, LONG lParam)
  331. {
  332.     MENULST    *pList;
  333.     WORD    wIdd;
  334.     WORD    wRc;
  335.     HMENU    hMenu;
  336.  
  337.     switch (wMsg) {
  338.     case WM_CREATE:            /* create child (modeless) dialog */
  339.         lpitMain = MakeProcInstance((FARPROC) MainDlg, hInst);
  340.         hwndDialog = CreateDialog(hInst, MAKEINTRESOURCE(2), hWnd, lpitMain);
  341.         SetHook(hWnd);
  342.         break;
  343.  
  344.     case WM_SETFOCUS:        /* If get focus, give it to dialog */
  345.         SetFocus(hwndDialog);
  346.         break;
  347.  
  348.     case WM_CLOSE:            /* send close message to child */
  349.         PostMessage(hwndDialog, wMsg, wParam, lParam);
  350.         break;
  351.  
  352.     case WM_INITMENU:        /* enable menu options as appropriate based upon the */
  353.         EnableMenus(hWnd);        /*    contents of the child dialog             */
  354.         break;
  355.  
  356.     case WM_SIZE:            /* if iconic, hide window if supposed to */
  357.         if ((wParam == SIZEICONIC) && bHidden)
  358.         PostMessage(hwndMain, PM_HIDE, 0, 0L);
  359.         break;
  360.  
  361.     case PM_HIDE:            /* hide: called by WM_SIZE: need to use PostMessage() */
  362.         ShowWindow(hwndMain, SW_HIDE);
  363.         break;
  364.  
  365.     case PM_ADDTOMENU:        /* this is called by amfilter.dll for new menus */
  366.         hMenu = GetSystemMenu(wParam, 0);
  367.         if (GetMenuState(hMenu, wFirst, MF_BYCOMMAND) == (WORD) -1) {
  368.         pList = mlFirst.pNext;
  369.         wIdd  = wFirst;
  370.         if (pList && (nSeparator == SEP_LINE))
  371.             AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);
  372.         while (pList) {        /* search linked list */
  373.             if (pList->szText)
  374.             if ((wIdd == wFirst) && (nSeparator == SEP_BREAK))
  375.                 AppendMenu(hMenu, MF_STRING | MF_ENABLED | MF_MENUBARBREAK, wIdd, pList->szText);
  376.             else
  377.                 AppendMenu(hMenu, MF_STRING | MF_ENABLED, wIdd, pList->szText);
  378.             wIdd += IDM_INCREMENT;
  379.             pList = pList->pNext;
  380.         }
  381.         }
  382.         break;
  383.  
  384.     case PM_MENUOPTION:             /* msg from dll saying that option selected from menu */
  385.         if ((wParam >= wFirst) && (wParam < SC_SIZE)) {
  386.         if (wParam == wLastCmd)        /* all because of Write in Windows 3.1! */
  387.             if ((GetTickCount() - dwLastCmdTicks) < TICKTHRESH)
  388.             break;
  389.         dwLastCmdTicks    = GetTickCount();
  390.         wLastCmd    = wParam;
  391.         pList        = mlFirst.pNext;
  392.         wIdd        = wFirst;
  393.         while(pList) {        /* search linked list for appropriate ID number */
  394.             if (wParam == wIdd) {
  395.             if (pList->szCommand) {
  396.                 wRc = WinExec(pList->szCommand, SW_SHOWNORMAL);
  397.                 if (wRc < 32)    /* crude, I know */
  398.                 ErrorBox(NULL, "WinExec error %d", wRc);
  399.             } else
  400.                 MessageBox(NULL, "This option has no command associated with it.",
  401.                     "System Menu Error", MB_ICONINFORMATION | MB_OK);
  402.             break;
  403.             }
  404.             wIdd += IDM_INCREMENT;    /* note: increment by 0x10 because system  */
  405.             pList = pList->pNext;    /*      menus apparently use lower 4 bits */
  406.         }
  407.         }
  408.         break;
  409.  
  410.     case WM_COMMAND:        /* Pass any menu commands to dialog */
  411.         switch (wParam) {
  412.         case IDM_NEW:
  413.         case IDM_SAVE:
  414.         case IDM_DELETE:
  415.         case IDM_PICK:
  416.         case IDM_EXIT:
  417.         case IDM_ABOUT:
  418.             SendMessage(hwndDialog, wMsg, wParam, lParam);
  419.             break;
  420.         default:
  421.             return(DefWindowProc(hWnd, wMsg, wParam, lParam));
  422.         } /* switch wParam */
  423.         break;
  424.  
  425.     case WM_DESTROY:            /* 0.3.1: somehow this got omitted */
  426.         PostQuitMessage(0);
  427.         break;
  428.  
  429.     /* 0.3.1: Since the combo box is in the child, but we're processing mouse input
  430.      * here, we have to perform the same sort of processing that DefDlgProc would.
  431.      */
  432.     case WM_LBUTTONDOWN:
  433.     case WM_NCLBUTTONDOWN:
  434.         if ((GetFocus() == hwndCombo) || (GetParent(GetFocus()) == hwndCombo))
  435.         SendMessage(hwndCombo, CB_SHOWDROPDOWN, FALSE, 0L);
  436.         return(DefWindowProc(hWnd, wMsg, wParam, lParam));
  437.  
  438.     default:
  439.         return(DefWindowProc(hWnd, wMsg, wParam, lParam));
  440.     }
  441.  
  442.     return FALSE;
  443. }
  444.  
  445.  
  446. /* AboutDlg
  447.  *
  448.  * This routine displays the about dialog box.
  449.  */
  450.  
  451. #pragma argsused
  452.  
  453. int FAR PASCAL AboutDlg(HWND hDlg, WORD wMsg, WORD wParam, LONG lParam)
  454. {
  455.     switch (wMsg) {
  456.     case WM_INITDIALOG:
  457.         SetDlgItemText(hDlg, IDD_TITLE, VERSION);
  458.         SetDlgItemText(hDlg, IDD_DATE,  VERDATE);
  459.         break;
  460.  
  461.     case WM_CLOSE:                /* did we manually close dlg? */
  462.         EndDialog(hDlg, FALSE);
  463.         break;
  464.  
  465.     case WM_COMMAND:
  466.         switch (wParam) {
  467.         case IDD_OK:            /* did we push ok? */
  468.             EndDialog(hDlg, FALSE);
  469.             break;
  470.         default:
  471.             return FALSE;
  472.         } /* switch wParam */
  473.         break;
  474.  
  475.     default:
  476.         return FALSE;
  477.     } /* switch wMsg */
  478.     return TRUE;
  479. } /* AboutDlg */
  480.  
  481.  
  482. /* MainDlg
  483.  *
  484.  * This modeless dialog box provides the lion's share of the processing in
  485.  * this program.  This is really the main callback function.
  486.  */
  487. BOOL FAR PASCAL MainDlg(HWND hDlg, WORD wMsg, WORD wParam, LONG lParam)
  488. {
  489.     int        i;
  490.     int        nIndex;
  491.     MENULST    *pList, *pPrev;
  492.  
  493.     switch (wMsg) {
  494.  
  495.     /* initialize the dialog */
  496.  
  497.     case WM_INITDIALOG:
  498.         /* Fill the combobox */
  499.  
  500.         pList = mlFirst.pNext;
  501.         while(pList) {
  502.         SendDlgItemMessage(hDlg, IDD_MENUNAME, CB_ADDSTRING, 0,
  503.                         (DWORD) (LPSTR) pList->szText);
  504.         pList = pList->pNext;
  505.         }
  506.  
  507.         CheckDlgButton(hDlg, IDD_HIDDEN, bHidden);
  508.         switch (nSeparator) {
  509.         case SEP_NONE:
  510.             CheckDlgButton(hDlg, IDD_SEPARATOR, 0);
  511.             CheckDlgButton(hDlg, IDD_NEWCOL, 0);
  512.             break;
  513.         case SEP_LINE:
  514.             CheckRadioButton(hDlg, IDD_SEPARATOR, IDD_NEWCOL, IDD_SEPARATOR);
  515.             break;
  516.         case SEP_BREAK:
  517.             CheckRadioButton(hDlg, IDD_SEPARATOR, IDD_NEWCOL, IDD_NEWCOL);
  518.             break;
  519.         }
  520.  
  521.         /* Set text limits */
  522.  
  523.         SendDlgItemMessage(hDlg, IDD_MENUNAME, CB_LIMITTEXT, STRING_MAX, 0L);
  524.         SendDlgItemMessage(hDlg, IDD_FILENAME, EM_LIMITTEXT, STRING_MAX, 0L);
  525.  
  526.         /* initialize routine's variables */
  527.  
  528.         bChanges     = FALSE;
  529.         bChangesMade = FALSE;
  530.         nIndex     = CB_ERR-1;
  531.         hwndCombo     = GetDlgItem(hDlg, IDD_MENUNAME);
  532.  
  533.         /* and prepare to draw screen */
  534.  
  535.         PostMessage(hDlg, PM_UPDATE, 0, 0L);
  536.  
  537.         break;
  538.  
  539.     /* if this gets focus, pass it to menu name field */
  540.  
  541.     case WM_SETFOCUS:
  542.         SetFocus(GetDlgItem(hDlg, IDD_MENUNAME));
  543.         break;
  544.  
  545.     case WM_CLOSE:            /* close: save changes? warn user */
  546.         SendMessage(hDlg, PM_QUERYSAVE, 0, 0L);
  547.  
  548.         if (IDYES != MessageBox(hDlg, "If you quit this program, you will not be "
  549.             "able to take advantage of the System Menu additions.  If you "
  550.             "wish to make use of the additions, please simply minimize "
  551.             "the program.  Do you REALLY want to quit?", "Quit?",
  552.             MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2))
  553.         break;
  554.         FreeHook();
  555.         ProcessWindows();
  556.  
  557.         DestroyWindow(hwndMain);
  558.         break;
  559.  
  560.     case PM_QUERYSAVE:        /* do you want to save? */
  561.         if (bChanges) {
  562.         if (MessageBox(hDlg, "Changes have been made."
  563.                 "  Do you want to save the changes?",
  564.                 "Save?",
  565.                 MB_YESNO | MB_ICONQUESTION) == IDYES)
  566.             SendMessage(hDlg, WM_COMMAND, IDM_SAVE, 0L);
  567.         bChanges = FALSE;
  568.         }
  569.         break;
  570.  
  571.     case PM_UPDATE:            /* fill in the contents of dialog */
  572.  
  573.         if (!GetDlgItemText(hDlg, IDD_MENUNAME, lpMenuName, STRING_MAX))
  574.         i = CB_ERR;
  575.         else
  576.         i = FindExactInCB(hDlg, IDD_MENUNAME, lpMenuName);
  577.  
  578.         if (!lstrlen(lpPrevName))
  579.         nIndex = CB_ERR-1;
  580.  
  581.         lstrcpy(lpPrevName, lpMenuName);
  582.  
  583.         if (nIndex == i)
  584.         break;
  585.  
  586.         nIndex = i;
  587.  
  588.         if (nIndex == CB_ERR) {
  589.         SetDlgItemText(hDlg, IDD_FILENAME, "");
  590.         EnableWindow(GetDlgItem(hDlg, IDD_FILENAME),
  591.             GetWindowTextLength(GetDlgItem(hDlg, IDD_MENUNAME)));
  592.         EnableWindow(GetDlgItem(hDlg, IDD_FILEPROMPT),
  593.             GetWindowTextLength(GetDlgItem(hDlg, IDD_MENUNAME)));
  594.         bChanges = FALSE;
  595.         break;
  596.         }
  597.  
  598.         EnableWindow(GetDlgItem(hDlg, IDD_FILENAME), TRUE);
  599.         EnableWindow(GetDlgItem(hDlg, IDD_FILEPROMPT), TRUE);
  600.  
  601.         if (GetPrivateProfileString(lpOptions, lpMenuName, "", lpFilename, STRING_MAX, lpProfile))
  602.         SetDlgItemText(hDlg, IDD_FILENAME, lpFilename);
  603.         else
  604.         SetDlgItemText(hDlg, IDD_FILENAME, "");
  605.  
  606.         bChanges = FALSE;
  607.         break;
  608.  
  609.     case WM_COMMAND:
  610.         switch (wParam) {
  611.  
  612.         /* Group name (folder) combo box message */
  613.  
  614.         case IDD_MENUNAME:
  615.             switch (HIWORD(lParam)) {
  616.             case CBN_SELCHANGE:
  617.             case CBN_EDITCHANGE:
  618.             case CBN_EDITUPDATE:
  619.                 SendMessage(hDlg, PM_QUERYSAVE, 0, 0L);
  620.                 PostMessage(hDlg, PM_UPDATE, 0, 0L);
  621.                 break;
  622.  
  623.             case CBN_ERRSPACE:
  624.                 ErrorBox(hDlg, "MainDlg:  CBN_ERRSPACE");
  625.                 break;
  626.  
  627.             default:
  628.                 return FALSE;
  629.             }
  630.             break;
  631.  
  632.         /* Any edit parameters change? */
  633.  
  634.         case IDD_FILENAME:
  635.             switch (HIWORD(lParam)) {
  636.             case EN_CHANGE:
  637.                 bChanges = TRUE;
  638.                 break;
  639.  
  640.             case EN_ERRSPACE:
  641.                 ErrorBox(hDlg, "ParamDlg:  EN_ERRSPACE");
  642.                 break;
  643.  
  644.             default:
  645.                 return FALSE;
  646.             }
  647.             break;
  648.  
  649.         /* Have check boxes been clicked? */
  650.  
  651.         case IDD_HIDDEN:        /* hidden check box used */
  652.             if (HIWORD(lParam) == BN_CLICKED) {
  653.             bHidden = !IsDlgButtonChecked(hDlg, wParam);
  654.             CheckDlgButton(hDlg, wParam, bHidden);
  655.             WritePrivateProfileInt(lpClass, lpHidden, bHidden, lpProfile);
  656.             } else
  657.             return FALSE;
  658.             break;
  659.  
  660.         case IDD_SEPARATOR:
  661.         case IDD_NEWCOL:
  662.             if (HIWORD(lParam) != BN_CLICKED)
  663.             return FALSE;
  664.             ProcessWindows();        /* before farting with nSeparator,       */
  665.                         /* get rid of existing separators if any */
  666.             if (IsDlgButtonChecked(hDlg, wParam)) {
  667.             CheckDlgButton(hDlg, wParam, 0);
  668.             nSeparator = SEP_NONE;
  669.             } else {
  670.             CheckRadioButton(hDlg, IDD_SEPARATOR, IDD_NEWCOL, wParam);
  671.             nSeparator = (wParam == IDD_SEPARATOR ? SEP_LINE : SEP_BREAK);
  672.             }
  673.             WritePrivateProfileInt(lpClass, lpSeparator, nSeparator, lpProfile);
  674.             break;
  675.         case IDM_DELETE:        /* select delete from menu */
  676.             if (GetDlgItemText(hDlg, IDD_MENUNAME, lpMenuName, STRING_MAX)) {
  677.             if (MessageBox(hDlg, "Are you sure?", "Delete",
  678.                 MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION) == IDNO) {
  679.                 break;
  680.             }
  681.             } else
  682.             break;
  683.  
  684.             i = FindExactInCB(hDlg, IDD_MENUNAME, lpMenuName);
  685.             if (i != CB_ERR) {
  686.             SendDlgItemMessage(hDlg, IDD_MENUNAME, CB_SETCURSEL, -1, 0L);
  687.             if (CB_ERR == SendDlgItemMessage(hDlg, IDD_MENUNAME,
  688.                         CB_DELETESTRING, i, 0L))
  689.                 ErrorBox(hDlg, "Delete: invalid index");
  690.             }
  691.  
  692.             SetDlgItemText(hDlg, IDD_MENUNAME, "");
  693.  
  694.             /* delete from profile file */
  695.  
  696.             WritePrivateProfileString(lpOptions, lpMenuName, NULL, lpProfile);
  697.             bChangesMade = TRUE;
  698.  
  699.             /* remove from all system menus */
  700.  
  701.             ProcessWindows();
  702.  
  703.             /* delete from linked list (keep node, remove szText) */
  704.  
  705.             pList = mlFirst.pNext;
  706.             pPrev = &mlFirst;
  707.             while (pList) {
  708.             if (!lstrcmp(lpMenuName, pList->szText)) {
  709.                 free(pList->szText);
  710.                 free(pList->szCommand);
  711.                 pPrev->pNext = pList->pNext;
  712.                 free(pList);
  713.                 pList = pPrev->pNext;
  714.             } else {
  715.                 pPrev = pList;
  716.                 pList = pList->pNext;
  717.             }
  718.             }
  719.  
  720.             /* and let's go back to what we were doing */
  721.  
  722.             SetFocus(hDlg);
  723.             PostMessage(hDlg, PM_UPDATE, 0, 0L);
  724.             break;
  725.  
  726.         case IDM_SAVE:            /* select save from menu */
  727.             if (!lpPrevName[0])
  728.             break;
  729.  
  730.             GetDlgItemText(hDlg, IDD_FILENAME, lpFilename, STRING_MAX);
  731.  
  732.             /* if the name isn't in listbox, add it and add it to the linked list */
  733.  
  734.             if (FindExactInCB(hDlg, IDD_MENUNAME, lpPrevName) == CB_ERR) {
  735.             SendDlgItemMessage(hDlg, IDD_MENUNAME, CB_ADDSTRING, 0, (DWORD) lpPrevName);
  736.  
  737.             /* add item to end of linked list */
  738.  
  739.             pList = &mlFirst;
  740.             while (pList->pNext)
  741.                 pList = pList->pNext;
  742.             pList->pNext     = malloc(sizeof(MENULST));
  743.             pList            = pList->pNext;
  744.             pList->pNext     = NULL;
  745.             pList->szText    = malloc(lstrlen(lpPrevName)+1);
  746.             pList->szCommand = malloc(lstrlen(lpFilename)+1);
  747.             lstrcpy(pList->szText,    lpPrevName);
  748.             lstrcpy(pList->szCommand, lpFilename);
  749.  
  750.             ProcessWindows();        /* add to system menus */
  751.             } else {        /* just update linked list entry */
  752.             pList = mlFirst.pNext;
  753.             while (pList) {
  754.                 if (!lstrcmp(lpPrevName, pList->szText)) {
  755.                 free(pList->szCommand);
  756.                 pList->szCommand = malloc(lstrlen(lpFilename)+1);
  757.                 lstrcpy(pList->szCommand, lpPrevName);
  758.                 }
  759.                 pList = pList->pNext;
  760.             }
  761.             }
  762.  
  763.             WritePrivateProfileString(lpOptions, lpPrevName, lpFilename, lpProfile);
  764.             bChanges = FALSE;
  765.             bChangesMade = TRUE;
  766.  
  767.             /* let's go back to what we were doing */
  768.  
  769.             PostMessage(hDlg, PM_UPDATE, 0, 0L);
  770.             SetFocus(hDlg);
  771.             break;
  772.  
  773.         /* Close button pressed */
  774.  
  775.         case IDM_EXIT:
  776.             PostMessage(hDlg, WM_CLOSE, 0, 0L);
  777.             break;
  778.  
  779.         /* Pick button pressed */
  780.  
  781.         case IDM_PICK:
  782.             PickFile(hDlg);
  783.             break;
  784.  
  785.         /* New option selected */
  786.  
  787.         case IDM_NEW:
  788.             SetDlgItemText(hDlg, IDD_MENUNAME, "");
  789.             PostMessage(hDlg, PM_UPDATE, 0, 0L);
  790.             PostMessage(hDlg, PM_UPDATE, 0, 0L);
  791.             break;
  792.  
  793.         case IDM_ABOUT:
  794.             Modal(hDlg, MAKEINTRESOURCE(1), (FARPROC) AboutDlg);
  795.             break;
  796.  
  797.         default:
  798.             return FALSE;
  799.         } /* switch wParam */
  800.         break;
  801.  
  802.     default:
  803.         return FALSE;
  804.     } /* switch wMsg */
  805.     return TRUE;
  806. } /* MainDlg */
  807.  
  808.  
  809. /* ErrorBox
  810.  *
  811.  * Print an error box message.    Accepts a variable number of arguments
  812.  * according to the ANSI standard.  The szFormat string is a simple format
  813.  * string like used with printf, permitting the use of "%d" (int), "%ld"
  814.  * (long int), "%f" (double), "%s" (char *), and "%Fs" (LPSTR).
  815.  */
  816. void ErrorBox(HWND hWnd, PSTR szFormat, ...)
  817. {
  818.     va_list args;
  819.     char    szString[STRING_MAX];
  820.  
  821.     va_start(args, szFormat);
  822.  
  823.     vsprintf(szString, szFormat, args);
  824.  
  825.     MessageBox(hWnd, szString, "Error", MB_ICONEXCLAMATION | MB_OK);
  826.  
  827.     return;
  828. } /* ErrorBox */
  829.  
  830.  
  831. /* Modal
  832.  *    This routine is the constructor for a modal dialog box.     Note that
  833.  *    with this definition, merely creating a item of type "ModalDialog"
  834.  *    will create a thunk instance and create the box.  The thunk is freed
  835.  *    in when done and the focus is returned to what is was on entry.
  836.  *    Note that there is no destructor because the instance is freed in
  837.  *    this routine (this is so the focus window could be recorded and
  838.  *    restored upon termination).
  839.  */
  840. void Modal(HWND hWnd, LPSTR lpName, FARPROC lpFunc)
  841. {
  842.     FARPROC lpitDialog;
  843.     HWND hWndFocus = GetFocus();
  844.  
  845.     if (!(lpitDialog = MakeProcInstance(lpFunc, hInst)))
  846.     ErrorBox(hWnd, "Modal constructor:  MakeProcInstance failure");
  847.     if (DialogBox(hInst, lpName, hWnd, lpitDialog) == -1)
  848.     ErrorBox(hWnd, "Modal constructor:  Unable to create dialog");
  849.     FreeProcInstance(lpitDialog);
  850.  
  851.     SetFocus(hWndFocus);
  852. } /* Modal */
  853.  
  854.  
  855. /* WritePrivateProfileInt
  856.  *
  857.  * While the system provides both GetProfileString and GetProfileInt,
  858.  * it only provide WriteProfileString.  This is the logical partner.
  859.  */
  860. void WritePrivateProfileInt(LPSTR lpAppName, LPSTR lpKeyName, int nInt, LPSTR lpFileName)
  861. {
  862.     static char szNumber[80];
  863.  
  864.     itoa(nInt, szNumber, 10);
  865.     WritePrivateProfileString(lpAppName, lpKeyName, szNumber, lpFileName);
  866.  
  867. } /* WritePrivateProfileInt */
  868.  
  869.  
  870. /* FindExactinCB
  871.  *
  872.  * The command to look up string in Combobox isn't too picky.  Let's find
  873.  * the one which precisely matches the string we're looking for.
  874.  */
  875. int FindExactInCB(HWND hDlg, int nIdd, LPSTR lpName)
  876. {
  877.     int   i;
  878.     char  szString[STRING_MAX];
  879.     LPSTR lpString = szString;
  880.  
  881.     i = SendDlgItemMessage(hDlg, nIdd, CB_FINDSTRING, -1, (DWORD) lpName);
  882.     if (i != CB_ERR)
  883.     SendDlgItemMessage(hDlg, nIdd, CB_GETLBTEXT, i, (DWORD) lpString);
  884.  
  885.     if (lstrcmp(lpString, lpName))
  886.     return CB_ERR;
  887.  
  888.     return i;
  889. } /* FindStringInCB */
  890.  
  891.  
  892. void EnableMenus(HWND hWnd)
  893. {
  894.     HMENU hMenu;
  895.     int      i;
  896.  
  897.     hMenu = GetMenu(hWnd);
  898.  
  899.     i = GetWindowTextLength(GetDlgItem(hwndDialog, IDD_MENUNAME));
  900.  
  901.     if (i) {
  902.     EnableMenuItem(hMenu, IDM_SAVE,      MF_BYCOMMAND | MF_ENABLED);
  903.     EnableMenuItem(hMenu, IDM_DELETE, MF_BYCOMMAND | MF_ENABLED);
  904.     EnableMenuItem(hMenu, IDM_PICK,      MF_BYCOMMAND | MF_ENABLED);
  905.     EnableMenuItem(hMenu, IDM_NEW,      MF_BYCOMMAND | MF_ENABLED);
  906.     } else {
  907.     EnableMenuItem(hMenu, IDM_SAVE,      MF_BYCOMMAND | MF_GRAYED);
  908.     EnableMenuItem(hMenu, IDM_DELETE, MF_BYCOMMAND | MF_GRAYED);
  909.     EnableMenuItem(hMenu, IDM_PICK,      MF_BYCOMMAND | MF_GRAYED);
  910.     EnableMenuItem(hMenu, IDM_NEW,      MF_BYCOMMAND | MF_GRAYED);
  911.     }
  912.  
  913.     /* this program can be neither maximized nor resized */
  914.  
  915.     hMenu = GetSystemMenu(hWnd, 0);
  916.     EnableMenuItem(hMenu, SC_SIZE,     MF_BYCOMMAND | MF_GRAYED);
  917.     EnableMenuItem(hMenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED);
  918.  
  919.     return;
  920. } /* EnableMenus */
  921.  
  922.  
  923. void PickFile(HWND hWnd)
  924. {
  925.     OPENFILENAME ofn;
  926.  
  927.     lstrcpy(lpFilename, WILDCARDS);
  928.  
  929.     ofn.lStructSize        = sizeof(OPENFILENAME);
  930.     ofn.hwndOwner        = hWnd;
  931.     ofn.hInstance        = hInst;
  932.     ofn.lpstrFilter        = lpFilter;
  933.     ofn.lpstrCustomFilter    = NULL;
  934.     ofn.nMaxCustFilter        = 0;
  935.     ofn.nFilterIndex        = 1;
  936.     ofn.lpstrFile        = lpFilename;
  937.     ofn.nMaxFile        = STRING_MAX;
  938.     ofn.lpstrFileTitle        = NULL;
  939.     ofn.nMaxFileTitle        = 0;
  940.     ofn.lpstrInitialDir        = NULL;
  941.     ofn.lpstrTitle        = lpPick;
  942.     ofn.Flags            = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
  943.     ofn.nFileOffset        = 0;
  944.     ofn.nFileExtension        = 0;
  945.     ofn.lpstrDefExt        = lpExe;
  946.     ofn.lCustData        = 0;
  947.     ofn.lpfnHook        = NULL;
  948.     ofn.lpTemplateName        = NULL;
  949.  
  950.     if (GetOpenFileName(&ofn))
  951.     SetDlgItemText(hWnd, IDD_FILENAME, lpFilename);
  952. } /* PickFile */
  953.  
  954.  
  955. void ProcessWindows(void)
  956. {
  957.     FARPROC    lpit = MakeProcInstance(EnumWindowsFunc, hInst);
  958.     HANDLE    hData;
  959.     int        i;
  960.  
  961.     if (!lpit) {
  962.     ErrorBox(hwndMain, "ProcessWindows: Error making instance thunk (1).");
  963.     return;
  964.     }
  965.     hData     = GlobalAlloc(GMEM_MOVEABLE, sizeof(HWND) * HANDLE_MAX);
  966.     if (!hData) {
  967.     ErrorBox(hwndMain, "ProcessWindows: Error creating data buffer.");
  968.     return;
  969.     }
  970.     hwndArray = (LPHWND) GlobalLock(hData);
  971.     if (!hwndArray) {
  972.     ErrorBox(hwndMain, "ProcessWindows: Error accessing data buffer.");
  973.     return;
  974.     }
  975.  
  976.     nCountArray = 0;
  977.     EnumWindows(lpit, 0L);
  978.     FreeProcInstance(lpit);
  979.  
  980.     lpit = MakeProcInstance(EnumChildrenFunc, hInst);
  981.     if (!lpit) {
  982.     ErrorBox(hwndMain, "ProcessWindows: Error making instance thunk (2).");
  983.     return;
  984.     }
  985.  
  986.     nCountWindow = 0;
  987.  
  988.     for (i = 0; i < nCountArray; i++)
  989.     EnumChildWindows(hwndArray[i], lpit, 0L);
  990.  
  991.     FreeProcInstance(lpit);
  992.  
  993. #if defined(DEBUG)
  994.     ErrorBox(hwndMain, "There were %d parents and %d children.", nCountArray, nCountWindow);
  995. #endif
  996.  
  997.     GlobalUnlock(hData);
  998.     GlobalFree(hData);
  999.  
  1000.     return;
  1001. } /* ProcessWindows */
  1002.  
  1003.  
  1004. #pragma argsused
  1005.  
  1006. BOOL FAR PASCAL EnumWindowsFunc(HWND hWnd, DWORD lParam)
  1007. {
  1008.     if (nCountArray >= HANDLE_MAX) {
  1009.     MessageBox(hwndMain, "Too many parents!", "Error", MB_ICONSTOP | MB_OK);
  1010.     return 0;
  1011.     }
  1012.  
  1013.     if (hWnd) {
  1014.     hwndArray[nCountArray] = hWnd;
  1015.     nCountArray++;
  1016.     }
  1017.  
  1018.     FixWindow(hWnd);
  1019.  
  1020.     return TRUE;
  1021. } /* EnumWindowsFunc */
  1022.  
  1023.  
  1024. #pragma argsused
  1025.  
  1026. BOOL FAR PASCAL EnumChildrenFunc(HWND hWnd, DWORD lParam)
  1027. {
  1028.     nCountWindow++;
  1029.     FixWindow(hWnd);
  1030.  
  1031.     return TRUE;
  1032. } /* EnumTaskWindows */
  1033.  
  1034.  
  1035. void FixWindow(HWND hWnd)
  1036. {
  1037.     HMENU   hMenu;
  1038.     MENULST *pList;
  1039.     WORD    wIdd;
  1040.     int        i;
  1041.  
  1042.     if (!hWnd) {
  1043.     return;                /* if no window, quit */
  1044.     }
  1045.     if (!(GetWindowLong(hWnd, GWL_STYLE) & WS_SYSMENU)) {
  1046.     return;                /* if no system menu in window, quit */
  1047.     }
  1048.  
  1049.     hMenu = GetSystemMenu(hWnd, 0);
  1050.     pList = mlFirst.pNext;
  1051.     wIdd  = wFirst;
  1052.  
  1053.     /* We're deleting all of them, so then do it.  Note that we also want to remove the
  1054.      * separator too, so this also removes the menu item before the first one found.
  1055.      * While this logic sound confusing, it is necessary to make sure that we haven't
  1056.      * deleted the first menu item.
  1057.      */
  1058.     if ((GetMenuState(hMenu, wIdd, MF_BYCOMMAND) != (WORD) -1) && (nSeparator == SEP_LINE)) {
  1059.     for (i = 1; i < GetMenuItemCount(hMenu); i++)
  1060.         if (GetMenuItemID(hMenu, i) == wIdd)
  1061.         DeleteMenu(hMenu, i-1, MF_BYPOSITION);
  1062.     }
  1063.     while (pList) {
  1064.     if (GetMenuState(hMenu, wIdd, MF_BYCOMMAND) != (WORD) -1)
  1065.         DeleteMenu(hMenu, wIdd, MF_BYCOMMAND);
  1066.     pList = pList->pNext;
  1067.     wIdd += IDM_INCREMENT;
  1068.     }
  1069.     return;
  1070. } /* FixWindow */
  1071.